﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace OvercomingShyness
{
    public class BadKeyMachine
    {
        private string[] mScript;
        private Dictionary<string, string> mGlobal;
        private Dictionary<string, string> mPersistent;
        private Dictionary<string, ValidCall> mDelegates;
        private Dictionary<string, int> mLabels;
        private List<BadKeyThread> mThreads;
        private BadKeyCompiler mCompiler;
        private bool mTrace;

        public bool Trace
        {
            get { return mTrace; }
            set { mTrace = value; }
        }

        public string[] Script
        {
            get { return mScript; }
        }

        public Dictionary<string, int> Labels
        {
            get { return mLabels; }
        }

        public Dictionary<string, ValidCall> Delegates
        {
            get { return mDelegates; }
        }
        
        /// <summary>
        /// Creates a new virtual machine around a script.
        /// </summary>
        /// <param name="s">script file to read from</param>
        public BadKeyMachine(string pFileName)
        {
            StreamReader s = new StreamReader(pFileName);

            // make new tables for threads delegates and labels
            mThreads = new List<BadKeyThread>();
            mDelegates = new Dictionary<string, ValidCall>();
            mLabels = new Dictionary<string, int>();
            mGlobal = new Dictionary<string, string>();
            mPersistent = new Dictionary<string, string>();

            // set default trace
            mTrace = false;

            // create a new compiler and compile the script
            mCompiler = new BadKeyCompiler(pFileName, this);
            mScript = mCompiler.Compile(s.ReadToEnd().Replace("\r", "").Split('\n'));
            
            // grab all function defs and labels
            for (int line = 0; line < mScript.Length; line++)
            {
                if (mScript[line].Split(' ')[0].Equals("def") || mScript[line].Split(' ')[0].Equals(":"))
                {
                    mLabels.Add(mScript[line].Split(' ')[1], line);
                }
            }
        }

        /// <summary>
        /// If debug mode is on, write text.
        /// </summary>
        /// <param name="pText"></param>
        internal void Debug(string pText)
        {
            System.Diagnostics.Debug.WriteLine(pText);
        }

        /// <summary>
        /// Spawns a new pseudo-thread
        /// </summary>
        /// <returns>spawned thread</returns>
        internal BadKeyThread Fork()
        {
            BadKeyThread thread = new BadKeyThread(this);
            mThreads.Add(thread);
            return thread;
        }

        /// <summary>
        /// Register a new external function with the virtual machine
        /// </summary>
        /// <param name="label">script's internal name for the function</param>
        /// <param name="call">delegate to call externally when function is called inside script</param>
        public void Register(string label, ValidCall call)
        {
            mDelegates[label] = call;
        }
        
        /// <summary>
        /// Spawn a new thread and call a function in it
        /// </summary>
        /// <param name="pLabel">function to call</param>
        /// <param name="pArguments">arguments to pass in</param>
        public void Call(string pLabel, string pArguments)
        {
            // spawn a new thread and pass the call along
            BadKeyThread newThread = Fork();
            Debug("Opening new thread: " + newThread.GetHashCode());
            newThread.Call(pLabel, pArguments);
        }

        /// <summary>
        /// Remove a thread from list of threads
        /// </summary>
        /// <remarks>
        /// Doesn't stop the thread from running or remove it from existance.
        /// That is handled by garbage collector. This should only really be called
        /// by the thread itself before dying.
        /// </remarks>
        /// <param name="pThread"></param>
        public void removeThread(BadKeyThread pThread)
        {
            mThreads.Remove(pThread);
        }

        public void StoreGlobal(string pLabel, string pValue)
        {
            mGlobal[pLabel] = pValue;
        }

        public void StorePersistent(string pLabel, string pValue)
        {
            mPersistent[pLabel] = pValue;
        }

        public string LoadGlobal(string pLabel)
        {
            return mGlobal[pLabel];
        }

        public string LoadPersistent(string pLabel)
        {
            return mPersistent[pLabel];
        }

        public Boolean HasGlobal(string pLabel)
        {
            return mGlobal.ContainsKey(pLabel);
        }

        public Boolean HasPersistent(string pLabel)
        {
            return mPersistent.ContainsKey(pLabel);
        }
    }
}
